<?php
/**
 * @link https://www.len168.com
 * @copyright Copyright (c) 2020/9/15 len168.com
 * @author toshcn <toshcn@foxmail.com>
 */

namespace common\components\oauth;

use Alipay\EasySDK\Kernel\EasySDKKernel;
use Yii;
use common\components\oauth\DefaultOauth;
use Alipay\EasySDK\Kernel\Factory;
use Alipay\EasySDK\Kernel\Config;

/**
 * 支付宝
 * @package common\components\oauth
 */
class Alipay extends DefaultOauth
{
    private $redirectUri = '';
    private $pid = '';
    private $options = null;

    /**
     * 初始化
     */
    public function init()
    {
        parent::init();
    }

    /**
     * 配置组件
     * @param array $config 配置数组
     */
    public function setConfig($config)
    {
        $options = new Config();
        $options->protocol = 'https';
        $options->gatewayHost = 'openapi.alipay.com';
        $options->signType = 'RSA2';

        if (is_array($config)) {
            // 支付宝appId
            $options->appId = isset($config['appId']) ? trim($config['appId']) : '';
            $this->redirectUri = isset($config['redirectUri']) ? trim($config['redirectUri']) : '';
            $this->pid = isset($config['pid']) ? trim($config['pid']) : '';

            // 为避免私钥随源码泄露，推荐从文件中读取私钥字符串而不是写入源码中
            //$options->merchantPrivateKey = '<-- 请填写您的应用私钥，例如：MIIEvQIBADANB ... ... -->';
            $options->merchantPrivateKey = isset($config['merchantPrivateKey']) ? trim($config['merchantPrivateKey']) : '';

            //$options->alipayCertPath = '<-- 请填写您的支付宝公钥证书文件路径，例如：/foo/alipayCertPublicKey_RSA2.crt -->';
            //$options->alipayRootCertPath = '<-- 请填写您的支付宝根证书文件路径，例如：/foo/alipayRootCert.crt" -->';
            //$options->merchantCertPath = '<-- 请填写您的应用公钥证书文件路径，例如：/foo/appCertPublicKey_2019051064521003.crt -->';
            $options->alipayCertPath = __DIR__ . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'alipay' . DIRECTORY_SEPARATOR . 'alipayCertPublicKey_' . $options->signType . '.crt';
            $options->alipayRootCertPath = __DIR__ . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'alipay' . DIRECTORY_SEPARATOR . 'alipayRootCert.crt';
            $options->merchantCertPath = __DIR__ . DIRECTORY_SEPARATOR . 'cert' . DIRECTORY_SEPARATOR . 'alipay' . DIRECTORY_SEPARATOR . 'appCertPublicKey_' . $options->appId . '.crt';


            //注：如果采用非证书模式，则无需赋值上面的三个证书路径，改为赋值如下的支付宝公钥字符串即可
            //$options->alipayPublicKey = '<-- 请填写您的支付宝公钥，例如：MIIBIjANBg... -->';
            $options->alipayPublicKey = isset($config['alipayPublicKey']) ? trim($config['alipayPublicKey']) : '';

            //可设置异步通知接收服务地址（可选）
            //$options->notifyUrl = "<-- 请填写您的支付类接口异步通知接收服务地址，例如：https://www.test.com/callback -->";
            $options->notifyUrl = isset($config['notifyUrl']) ? trim($config['notifyUrl']) : '';

            //可设置AES密钥，调用AES加解密相关接口时需要（可选）
            //$options->encryptKey = "<-- 请填写您的AES密钥，例如：aa4BtZ4tspm2wnXLb1ThQA== -->";
            $options->encryptKey = isset($config['encryptKey']) ? trim($config['encryptKey']) : '';

        }

        $this->options = $options;
        // 设置参数
        Factory::setOptions($this->options);
    }

    /**
     * @param $code
     * @return array
     */
    public function getToken($code)
    {
        try {
            //2. 发起API调用
            $result = Factory::Base()->OAuth()->getToken($code);
            //3. 处理响应或异常
            if (isset($result->accessToken) && $result->accessToken) {
                return ['status' => 1, 'data' => ['token' => $result->accessToken, 'account' => $result->userId]];
            }
            return ['status' => 0, 'data' => [], 'message' => "接口调用失败：". $result->msg."，".$result->subMsg];
        } catch (\Exception $e) {
            return ['status' => 0, 'data' => [], 'message' => $e->getMessage()];
        }
    }

    /**
     * 获取用户数据
     * @param $token
     * @return array
     */
    public function getUserShareInfo($token)
    {
        try {
            //2. 发起API调用
            $result = Factory::util()->generic()->execute('alipay.user.info.share', ['auth_token' => $token], []);
            //3. 处理响应或异常
            if (!empty($result->code) && $result->code == 10000) {
                $body = json_decode($result->httpBody, true);
                return ['status' => 1, 'data' => $body['alipay_user_info_share_response']];
            }
            return ['status' => 0, 'data' => $result, 'message' => "接口调用失败：". $result->msg."，".$result->subMsg];
        } catch (\Exception $e) {
            return ['status' => 0, 'data' => [], 'message' => $e->getMessage()];
        }
    }

    /**
     * @param string $refreshToken
     * @return mixed|void
     */
    public function refreshToken($refreshToken)
    {
        // TODO: Implement refreshToken() method.
    }

    /**
     * @return mixed|void
     */
    public function createOauthUrl()
    {
        // TODO: Implement createOauthUrl() method.
    }

    /**
     * 创建App端授权信息
     * @return mixed|string
     * @throws \yii\base\Exception
     */
    public function createOauthInfo()
    {
        $state = Yii::$app->getSecurity()->generateRandomString();
        Yii::$app->getCache()->set(Yii::$app->util->cacheKey($state), $state, 3600);
        list($msec, $sec) = explode(' ', microtime());

        $mt = $sec . round($msec * 1000);
        $signStr = "apiname=com.alipay.account.auth&app_id={$this->options->appId}&app_name=mc&auth_type=AUTHACCOUNT&biz_type=openservice&method=alipay.open.auth.sdk.code.get&pid={$this->pid}&product_id=APP_FAST_LOGIN&scope=kuaijie&sign_type=RSA2&target_id={$mt}";
        $sign = $this->sign($signStr, $this->options->signType);

        // 返回签名后的字符串
        return $signStr . '&sign=' . urlencode($sign);
    }

    /**
     * 签名
     * @param string $signStr 待签名字符串
     * @param string $signType 加密方式
     * @return string
     */
    public function sign($signStr, $signType = 'RSA2')
    {
        /** @value $this->options->merchantPrivateKey RSA私钥 */
        $res = "-----BEGIN RSA PRIVATE KEY-----\n" .
            wordwrap($this->options->merchantPrivateKey, 64, "\n", true) .
            "\n-----END RSA PRIVATE KEY-----";

        if ($signType == 'RSA2') {
            openssl_sign($signStr, $sign, $res, OPENSSL_ALGO_SHA256);
        } else {
            openssl_sign($signStr, $sign, $res);
        }
        $sign = base64_encode($sign);
        return $sign;
    }
}
